package aml.parse;

import java.io.BufferedReader;
import java.io.IOException;

public class Scanner {
	private String str;
	private int pos = -1;	// points to the character already scanned
	private char c;
	private StringBuilder sb = new StringBuilder();
	
	public Scanner(BufferedReader reader) throws IOException {
		String line;
		while ((line = reader.readLine()) != null) {
			sb.append(line).append("\n");
		}
		this.str = sb.toString();
		reader.close();
	}
	
	public boolean hasNext() {
		return peek() > 0;
	}

	public Token next() {
		while (isSpace(nextChar()) || hasComment()) ;
		
		if (isEOF(c)) return Token.EOF;
		if (isEOL(c)) return Token.EOL;
		
		if (isAlpha(c) || isDollar(c) || isAt(c)) {
			clearBuffer();
			do {
				append(c);
				nextChar();
			} while (isAlpha(c) || isDigit(c) || isUnderscore(c) || isDollar(c) || isAt(c));
			putBack();
			return Token.sym(getBuffer());
		}
		
		if (isDigit(c)) {
			clearBuffer();
			do {
				append(c);
				nextChar();
			} while (isDigit(c));
			putBack();
			return Token.num(getBuffer());
		}
		
		if (isQuote(c)) {
			clearBuffer();
			while (true) {
				nextChar();
				if (isBackSlash(c)) {
					nextChar();
					if (c == 'n') append('\n');
					else if (c == 'r') append('\r');
					else if (c == 't') append('\t');
					else append(c);
				}
				else if (isQuote(c)) {
					break;
				}
				append(c);
			}
			return Token.str(getBuffer());
		}

		if (isGroupQuote(c)) {
			clearBuffer();
			append(c);
			return Token.op(getBuffer());
		}
		
		if (isOperator(c)) {
			clearBuffer();
			do {
				append(c);
				nextChar();
			} while (isOperator(c));
			putBack();
			return Token.op(getBuffer());
		}
		
		System.err.println("Illegal character: " + c);
		return Token.EOF;
	}
	
	private char charAt(int p) {
		if (p >= str.length()) return 0;
		return str.charAt(p);
	}
	
	private char peek(int i) {
		return charAt(pos + i);
	}
	
	private char peek() {
		return peek(1);
	}
	
	private boolean isUnderscore(char c) {
		return c == '_';
	}
	
	private boolean isLowercase(char c) {
		return 'a' <= c && c <= 'z';
	}
	
	private boolean isUppercase(char c) {
		return 'A' <= c && c <= 'Z';
	}
	
	private boolean isAlpha(char c) {
		return isLowercase(c) || isUppercase(c);
	}
	
	private boolean isDigit(char c) {
		return '0' <= c && c <= '9';
	}
	
	private boolean isDollar(char c) {
		return c == '$';
	}
	
	private boolean isAt(char c) {
		return c == '@';
	}
	
	private boolean isOperator(char c) {
		return ":,;.~!#%^&*-+/=|<>?\\".indexOf(c) != -1;
	}
	
	private boolean isGroupQuote(char c) {
		return "()[]{}".indexOf(c) != -1;
	}
	
	private boolean isQuote(char c) {
		return c == '"';
	}
	
	private boolean isBackSlash(char c) {
		return c == '\\';
	}
	
	private boolean isSlash(char c) {
		return c == '/';
	}
	
	private boolean isStar(char c) {
		return c == '*';
	}
	
	private boolean isEOF(char c) {
		return c == 0;
	}
	
	private boolean isEOL(char c) {
		return c == '\r' || c == '\n';
	}
	
	private void slashSlashComment() {
		while (!isEOL(peek())) nextChar();
	}
	
	private void slashStarComment() {
		while (!isEOF(peek())) {
			if (isSlash(nextChar()) && isStar(peek())) {
				nextChar();
				slashStarComment();
			}
			if (isStar(c) && isSlash(peek())) {
				nextChar();
				break;
			}
		}
	}
	
	private boolean isSpace(char c) {
		return c == ' ' || c == '\t';
	}
	
	private char nextChar() {
		return c = charAt(++pos);
	}
	
	private void putBack() {
		--pos;
	}
	
	private boolean hasComment() {
		if (isSlash(c)) {
			if (isSlash(peek())) {
				nextChar();
				slashSlashComment();
				return true;
			}
			else if (isStar(peek())) {
				nextChar();
				slashStarComment();
				return true;
			}
		}
		return false;
	}
	
	private void clearBuffer() {
		sb.setLength(0);
	}
	
	private String getBuffer() {
		return sb.toString();
	}
	
	private void append(char c) {
		sb.append(c);
	}
}